package repo

import (
	"context"
	"gitee.com/byx_darwin/geo-rpc/internal/base/data"
	"gitee.com/byx_darwin/geo-rpc/internal/service"
	"gitee.com/byx_darwin/geo-rpc/kitex_gen/geo"
	"gitee.com/byx_darwin/micro-extend/pkg/errors/rpc"
	"github.com/baoyxing/go-tools/cache/v1"
	v1Cache "github.com/baoyxing/go-tools/pkg/cache/v1"
	"github.com/pkg/errors"
	"net"
	"strconv"
	"strings"
)

type geoRepo struct {
	*data.Data
	cache v1.Cache[string, *geo.GeoObject]
}

func NewGeoRepo(data *data.Data) service.GeoRepo {
	cache := v1Cache.NewLruCache[string,
		*geo.GeoObject](data.Conf.GeoOption.CacheSize)
	return &geoRepo{data, cache}
}

func (repo *geoRepo) Get(ctx context.Context,
	ip string,
	region geo.Region) (*geo.GeoObject, error) {
	key := strconv.Itoa(int(region)) + "-" + ip
	geoIPObj := new(geo.GeoObject)
	var err error
	if repo.Conf.GeoOption.CacheSize > 0 {
		geoIPObj, ok := repo.cache.Get(key)
		if ok {
			return geoIPObj, nil
		}
	}
	switch region {
	case geo.Region_CN:
		geoIPObj, err = repo.getLocation(ctx, ip)
		if err != nil {
			return nil, err
		}
		tempObject, err := repo.cityRecord(ctx, ip)
		if err != nil {
			return nil, err
		}
		geoIPObj.Continent = tempObject.Continent
		geoIPObj.Latitude = tempObject.Latitude
		geoIPObj.Longitude = tempObject.Longitude
	case geo.Region_Overseas:
		geoIPObj, err = repo.cityRecord(ctx, ip)
		if err != nil {
			return nil, err
		}
		tempObject, err := repo.ispRecord(ctx, ip)
		if err != nil {
			return nil, err
		}
		geoIPObj.Isp = tempObject.Isp
	}
	if repo.Conf.GeoOption.CacheSize > 0 {
		repo.cache.Set(key, geoIPObj)
	}
	return geoIPObj, nil
}

func (repo *geoRepo) cityRecord(ctx context.Context,
	ip string) (*geo.GeoObject, error) {
	cityRecord, err := repo.CityDB.City(net.ParseIP(ip))
	if err != nil {
		return nil, rpc.NewBizStatusError(rpc.ErrorTypeDataInvalid,
			errors.Wrap(err, "cityDB City failed"))
	}
	country := cityRecord.Country.Names[repo.Conf.GeoOption.Language]
	country = strings.ReplaceAll(country, " ", "")
	city := cityRecord.City.Names[repo.Conf.GeoOption.Language]
	city = strings.ReplaceAll(city, " ", "")
	continent := cityRecord.Continent.Names[repo.Conf.GeoOption.Language]
	continent = strings.ReplaceAll(continent, " ", "")
	return &geo.GeoObject{
		Country:   country,
		City:      city,
		Province:  "unknown",
		Latitude:  cityRecord.Location.Latitude,
		Longitude: cityRecord.Location.Longitude,
		Continent: continent,
	}, nil
}

func (repo *geoRepo) ispRecord(ctx context.Context,
	ip string) (*geo.GeoObject, error) {
	ispRecord, err := repo.IspDB.ISP(net.ParseIP(ip))
	if err != nil {
		return nil, rpc.NewBizStatusError(rpc.ErrorTypeDataInvalid,
			errors.Wrap(err, "ispDB ISP failed"))
	}
	return &geo.GeoObject{
		Isp: strings.ReplaceAll(ispRecord.ISP, " ", ""),
	}, nil
}

func (repo *geoRepo) getLocation(ctx context.Context,
	ip string) (*geo.GeoObject, error) {
	loc, err := repo.Data.Searcher.SearchByStr(ip)
	if err != nil {
		return nil, rpc.NewBizStatusError(rpc.ErrorTypeDataInvalid,
			errors.Wrap(err, "Searcher SearchByStr failed"))
	}
	locArr := strings.Split(loc, "|")
	country, region, city, isp := "unknown", "unknown", "unknown", "unknown"
	if len(locArr) == 5 {
		country = locArr[0]
		region = locArr[2]
		city = locArr[3]
		isp = locArr[4]
	}
	if country == "0" {
		country = "unknown"
	}
	if region == "0" {
		region = "unknown"
	} else {
		arr := strings.Split(region, "省")
		if len(arr) > 0 {
			region = arr[0]
		}
	}
	if city == "内网IP" || city == "0" {
		city = "unknown"
	} else {
		arr := strings.Split(city, "市")
		if len(arr) > 0 {
			city = arr[0]
		}
	}
	return &geo.GeoObject{
		Country:  strings.ReplaceAll(country, " ", ""),
		Province: strings.ReplaceAll(region, " ", ""),
		City:     strings.ReplaceAll(city, " ", ""),
		Isp:      strings.ReplaceAll(isp, " ", ""),
	}, nil
}
